﻿using Newtonsoft.Json;
using Sustainalytics.DataLayer.Exceptions;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.Entities;
using Sustainalytics.Portfolios.BL.Exceptions;
using Sustainalytics.Portfolios.BL.Model;
using Sustainalytics.Portfolios.BL.Validators;
using Sustainalytics.Replication.Publisher;
using Sustainalytics.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using Sustainalytics.ClientDW.Entities;
using MongoDB.Driver;
using Sustainalytics.DataLayer.EsgRatings;
using Sustainalytics.Entities.ESGRatings;
using CarteConnection;
using Sustainalytics.Companies;
using System.Configuration.Abstractions;

namespace Sustainalytics.Portfolios.BL
{


    public class CustomPortfolioUpdateHandler : ICustomPortfolioUpdateHandler
    {
        private readonly IRepositoryCollection<PortfolioDetails> _portfolioDetailsRepository;

        private readonly IRepositoryCollection<PortfolioMatches> _portfolioMatchesRepository;

        private readonly IEsgInputFileRepository _esgInputFileRepository;

        private readonly IRepositoryCollection<Portfolio> _portfolioRepository;

        private readonly IRepositoryCollection<PortfolioUserInput> _securitiesUserInputRepository;

        private readonly ServiceBusPublisher _serviceBusPublisher;

        private readonly ICompanyMatchingLogic _companyMatchingLogic;

        private IPortfolioMatching _portfolioMatching;

        private IScreeningToolFilterManager _screeningToolFilterManager;

        private ISubscribeToPortfolioManagement _subscriptionsManagement;

        private readonly string _cartePath;

        private readonly string _carteParameter;

        public CustomPortfolioUpdateHandler(
            IRepositoryCollection<Portfolio> portfolioRepository,
            IRepositoryCollection<PortfolioDetails> portfolioDetailsRepository,
            IRepositoryCollection<PortfolioMatches> portfolioMatchesRepository,
            IRepositoryCollection<PortfolioUserInput> userInputRepository,
            IEsgInputFileRepository esgInputFileRepository,
            ServiceBusPublisher serviceBusPublisher,
            ICompanyMatchingLogic companyMatchingLogic)
        {
            this._portfolioRepository = portfolioRepository;
            this._portfolioDetailsRepository = portfolioDetailsRepository;
            this._portfolioMatchesRepository = portfolioMatchesRepository;
            this._securitiesUserInputRepository = userInputRepository;
            this._esgInputFileRepository = esgInputFileRepository;
            this._serviceBusPublisher = serviceBusPublisher;
            this._companyMatchingLogic = companyMatchingLogic;
            this._cartePath = ConfigurationManager.Instance.AppSettings["CartePath"];
            this._carteParameter = ConfigurationManager.Instance.AppSettings["CarteParameter"];
        }

        public PortfolioSaveResponseMessage AddCompaniesToPortfolio(
            Guid portfolioId,
            string portfolioName,
            PortfolioSharingStatus sharingStatus,
            ICollection<Guid> companyIds,
            PortfolioUserClaims userClaims)
        {
            var capitalIdIds = _companyMatchingLogic.FindCapitalIdIdsForCompanies(companyIds);
            ICollection<PortfolioSecuritiesRow> securitiesInfo =
                this._portfolioMatching.GetMainSecurityForCompanies(capitalIdIds, userClaims);

            if (portfolioId == Guid.Empty)
            {
                this.AddPortfolioAndDetails(portfolioName, sharingStatus, securitiesInfo, userClaims);
            }
            else
            {
                this.ReplaceDetailsInPortfolio(portfolioId, sharingStatus, securitiesInfo, userClaims);
            }

            var saveResult = new PortfolioSaveResponseMessage
            {
                SavedWithErrors = securitiesInfo.Any(a => a.Security == new PortfolioSecurityModel.Null()),
                SecuritiesExists = false
            };
            return saveResult;
        }

        public PortfolioSaveResponseMessage AddCompanyToPortfolio(
            Guid portfolioId,
            Guid companyId,
            PortfolioUserClaims userClaims)
        {
            //get capitalIqId for Guid
            var id = _companyMatchingLogic.FindCapitalIqIdForGuid(companyId);
            var securitiesInfo = this._portfolioMatching.GetMainSecurityForCompany(id, userClaims);

            var saveResult = new PortfolioSaveResponseMessage
            {
                SavedWithErrors = !securitiesInfo.Any(),
                SecuritiesExists = this.SecurityAlreadyExists(portfolioId, securitiesInfo)
            };

            this.AppendDetailsToPortfolio(portfolioId, securitiesInfo, userClaims);

            return saveResult;
        }

        public Portfolio AddPortfolioAndDetails(
            string portfolioName,
            PortfolioSharingStatus sharingStatus,
            ICollection<PortfolioSecuritiesRow> details,
            PortfolioUserClaims userClaims)
        {
            Portfolio portfolio = PortfolioBuilder.Build(Guid.NewGuid(), portfolioName, sharingStatus, userClaims);
            portfolio.CompanyCount = details.Count(x => x.IsValid);
            var oldSharingStatus = portfolio.SharingStatus;
            this.AddDetailsToPortfolio(portfolio, oldSharingStatus, details, userClaims);
            return portfolio;
        }

        public Portfolio AppendDetailsToPortfolio(
            Guid portfolioId,
            ICollection<PortfolioSecuritiesRow> details,
            PortfolioUserClaims userClaims)
        {
            Portfolio portfolio = this._portfolioRepository.Read(portfolioId);
            if (portfolio == null)
            {
                throw new PortfolioNotFoundException();
            }
            portfolio.CompanyCount += details.Count(x => x.IsValid);
            this.AddDetailsToPortfolio(portfolio, portfolio.SharingStatus, details, userClaims);
            return portfolio;
        }

        public Portfolio ReplaceDetailsInPortfolio(Guid portfolioId, PortfolioSharingStatus sharingStatus,
                                           ICollection<PortfolioSecuritiesRow> details, PortfolioUserClaims usercClaim)
        {
            var portfolio = _portfolioRepository.Read(portfolioId);
            var oldSharingStatus = portfolio.SharingStatus;
            portfolio.CompanyCount = details.Count(x => x.IsValid);
            portfolio.SharingStatus = sharingStatus;

            _portfolioDetailsRepository.DeleteWhere(x => x.PortfolioId == portfolioId);
            _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(portfolioId), ReplicationUtils.EntityType.PortfolioDetails,
                                             ReplicationUtils.Operation.DeleteWhereByPortfolioId);

            _securitiesUserInputRepository.DeleteWhere(x => x.PortfolioId == portfolioId);
            _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(portfolioId), ReplicationUtils.EntityType.PortfolioUserInput,
                                             ReplicationUtils.Operation.DeleteWhereByPortfolioId);

            AddDetailsToPortfolio(portfolio, oldSharingStatus, details, usercClaim);

            return portfolio;
        }

        private void AddDetailsToPortfolio(
           Portfolio portfolio,
           PortfolioSharingStatus oldSharingStatus,
           ICollection<PortfolioSecuritiesRow> details,
           PortfolioUserClaims userclaims)
        {
            if (!details.IsNullOrEmpty())
            {
                if (!PortfolioValidator.Write().IsValid(portfolio, userclaims))
                {
                    throw new PortfolioModificationUnauthorizedException();
                }

                var portfolioUserInputs = new List<PortfolioUserInput>();
                var securities = new List<PortfolioDetails>();

                foreach (var detail in details)
                {
                    PortfolioSecurityModel security = detail.Security;
                    if (detail.IsValid)
                    {
                        security.PortfolioId = portfolio.Id;
                        if (security.Id == Guid.Empty)
                        {
                            security.Id = Guid.NewGuid();
                        }

                        securities.Add(security.GetPortfolioDetails());
                    }

                    var userInput = security.GetPortfolioUserInput(portfolio.AddedByUserId);
                    portfolioUserInputs.Add(userInput);
                }

                foreach (var portfolioUserInput in portfolioUserInputs)
                {
                    if (portfolioUserInput.Id == Guid.Empty)
                    {
                        portfolioUserInput.Id = Guid.NewGuid();
                        portfolioUserInput.PortfolioId = portfolio.Id;
                    }
                }

                this._portfolioDetailsRepository.CreateOrUpdateBatch(securities);
                _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(securities), ReplicationUtils.EntityType.PortfolioDetails, ReplicationUtils.Operation.CreateOrUpdateBatch);

                this._securitiesUserInputRepository.CreateOrUpdateBatch(portfolioUserInputs);
                _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(portfolioUserInputs), ReplicationUtils.EntityType.PortfolioUserInput, ReplicationUtils.Operation.CreateOrUpdateBatch);

                //sending portfolio id to link: cartePath"
                var carteConnection = new PortfolioUpdate(portfolio.Id, "Add Details To Portfolio", _cartePath, _carteParameter);
                carteConnection.SendPortfolioIdToCarte();
            }

            this.UpdatePortfolio(portfolio, oldSharingStatus);
        }


        public void DeletePortfolioSecurity(Guid portfolioId, Guid securityId, PortfolioUserClaims userClaims)
        {
            var portfolio = this._portfolioRepository.Read(portfolioId);

            if (!PortfolioValidator.Write().IsValid(portfolio, userClaims))
            {
                throw new PortfolioDetailsDeleteUnauthorisedException();
            }

            this._portfolioDetailsRepository.DeleteWhere(x => x.Id == securityId && x.PortfolioId == portfolioId);
            _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(new Tuple<Guid, Guid>(securityId, portfolioId)), ReplicationUtils.EntityType.PortfolioDetails, ReplicationUtils.Operation.DeleteWhereBySecurityIdAndPortfolioId);

            this._securitiesUserInputRepository.DeleteWhere(x => x.Id == securityId && x.PortfolioId == portfolioId);
            _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(new Tuple<Guid, Guid>(securityId, portfolioId)), ReplicationUtils.EntityType.PortfolioUserInput, ReplicationUtils.Operation.DeleteWhereBySecurityIdAndPortfolioId);

            portfolio.CompanyCount--;
            portfolio.DateModified = DateTime.Today;
            this._portfolioRepository.CreateOrUpdate(portfolio);
            _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(portfolio), ReplicationUtils.EntityType.Portfolio, ReplicationUtils.Operation.CreateOrUpdate);
        }

        public void SaveMatchedPortfolioDetails(string portfolioName, Guid accountId, Guid userId, string userName, string userFullName)
        {
            var portfolioMatches = _portfolioMatchesRepository.ReadAllWhere(x =>
                x.PortfolioName == portfolioName &&
                x.AccountId == accountId &&
                x.AddedByUserId == userId);

            if (portfolioMatches == null)
                throw new ArgumentNullException($"No portfolio matches found for PortfolioName: {portfolioName}, AccountId: {accountId} and UserId: {userId}");

            if (!portfolioMatches.Any())
                return;

            var companyCount = portfolioMatches.Count(x => x.Added);
            var sharingStatus = portfolioMatches.First().SharingStatus;
            var portfolio = _portfolioRepository.ReadAllWhere(x => x.Name == portfolioName &&
                x.AccountIds.Contains(accountId) &&
                x.AddedByUserId == userId).SingleOrDefault();

            if (portfolio == null)
            {
                portfolio = new Portfolio
                {
                    Id = Guid.NewGuid(),
                    Name = portfolioName,
                    DataCreated = DateTime.UtcNow,
                    DateModified = DateTime.UtcNow,
                    AccountIds = new Guid[] { accountId },
                    AddedByUserId = userId,
                    AddedByUserName = userName,
                    AddedByFullName = userFullName,
                    SharingStatus = sharingStatus,
                    CompanyCount = companyCount
                };
            }
            else
            {
                portfolio.DateModified = DateTime.UtcNow;
                portfolio.CompanyCount = companyCount;

                _portfolioDetailsRepository.DeleteWhere(x => x.PortfolioId == portfolio.Id);
                _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(portfolio.Id), ReplicationUtils.EntityType.PortfolioDetails, ReplicationUtils.Operation.DeleteWhereByPortfolioId);
            }

            foreach (var portfolioMatch in portfolioMatches)
                portfolioMatch.PortfolioId = portfolio.Id;
            UpdatePortfolio(portfolio, sharingStatus);

            var portfolioDetails = portfolioMatches.Select(x => x.ToPortfolioDetails());
            _portfolioDetailsRepository.CreateOrUpdateBatch(portfolioDetails);

            //sending portfolio id to link: cartePath"
            var carteConnection = new PortfolioUpdate(portfolio.Id, "Save Matched Portfolio Details", _cartePath, _carteParameter);
            carteConnection.SendPortfolioIdToCarte();

            ReplicatePortfolioDetails(portfolioDetails);
            _portfolioMatchesRepository.DeleteWhere(x => x.PortfolioName == portfolioName && x.AccountId == accountId && x.AddedByUserId == userId);
        }

        private void ReplicatePortfolioDetails(IEnumerable<PortfolioDetails> portfolioDetails)
        {
            var skipElements = 0;
            var takeElements = 200;

            while (portfolioDetails.Skip(skipElements).Take(takeElements).Count() != 0)
            {
                var portfolioDetailsBucket = portfolioDetails.Skip(skipElements).Take(takeElements);
                var jsonPortfolioDetails = JsonConvert.SerializeObject(portfolioDetailsBucket);
                skipElements += takeElements;
                _serviceBusPublisher.SendMessage(jsonPortfolioDetails, ReplicationUtils.EntityType.PortfolioDetails, ReplicationUtils.Operation.CreateOrUpdateBatch);
            }
        }

        public IList<PortfolioDetails> GetPortfolioDetailsBy(Guid portfolioId)
        {
            return _portfolioDetailsRepository.ReadAllWhere(x => x.PortfolioId == portfolioId);
        }

        public IList<PortfolioMatches> GetPortfolioMatchesBy(string portfolioName, Guid accountId, Guid userId)
        {
            return _portfolioMatchesRepository.ReadAllWhere(x => x.PortfolioName == portfolioName && x.AccountId == accountId && x.AddedByUserId == userId);
        }

        public IList<PortfolioMatches> SearchForSecurities(string securityName, string securityIdentifier, PortfolioUserClaims userClaims)
        {
            return this._portfolioMatching.GetSecurityRowForIdentifier(securityName, securityIdentifier, userClaims);
        }

        public void SetPortfolioMatching(IPortfolioMatching companySearch)
        {
            this._portfolioMatching = companySearch;
        }

        public void SetScreeningToolFilterManagement(IScreeningToolFilterManager repository)
        {
            this._screeningToolFilterManager = repository;
        }

        public void SetSubscriptionManager(ISubscribeToPortfolioManagement subscriptionManagement)
        {
            this._subscriptionsManagement = subscriptionManagement;
        }

        public void UpdatePortfolioStatus(Guid id, PortfolioSharingStatus status, PortfolioUserClaims userClaims)
        {
            var portfolio = this._portfolioRepository.Read(id);
            var oldSharingStatus = portfolio.SharingStatus;

            if (!PortfolioValidator.Write().IsValid(portfolio, userClaims))
            {
                throw new PortfolioModificationUnauthorizedException();
            }

            portfolio.SharingStatus = status;
            UpdatePortfolio(portfolio, oldSharingStatus);
        }

        public void SavePortfolioMatches(List<PortfolioMatches> securities)
        {
            this._portfolioMatchesRepository.CreateOrUpdateBatch(securities);
        }

        public List<PortfolioMatches> AddMatchesAndPortfolio(
            string portfolioName,
            PortfolioSharingStatus sharingStatus,
            ICollection<PortfolioMatches> matches,
            double? securityWeight,
            PortfolioUserClaims userClaims)
        {
            var securities = new List<PortfolioMatches>();

            if (!matches.IsNullOrEmpty())
            {
                var portfolioUserInputs = new List<PortfolioUserInput>();


                foreach (var match in matches)
                {
                    match.PortfolioName = portfolioName;
                    match.AddedByFullName = userClaims.UserFullName;
                    match.AddedByUserId = userClaims.UserId;
                    match.AddedByUserName = userClaims.UserName;
                    match.AccountId = userClaims.AccountId;
                    match.AddedFromCompanySearch = false;
                    match.CompanyNameLowercase = (match.MatchedInfo.CompanyName ?? string.Empty).ToLowerInvariant();
                    match.SharingStatus = sharingStatus;
                    match.SecurityId = (match.MatchedInfo.CapitalIQId == string.Empty) ? string.Empty : _companyMatchingLogic.FindSecurityIdForCompanyId(Convert.ToInt32(match.MatchedInfo.CapitalIQId));
                    match.SecurityWeight = securityWeight;
                    match.CompanyNameUrlFriendly = (match.MatchedInfo.CapitalIQId == string.Empty) ? string.Empty : _companyMatchingLogic.FindCompanyNameFriendlyURLForId(Convert.ToInt32(match.MatchedInfo.CapitalIQId));
                    securities.Add(match);

                    var userInput = match.GetPortfolioUserInput(userClaims.UserId);
                    portfolioUserInputs.Add(userInput);
                }
            }

            return securities;
        }

        private bool SecurityAlreadyExists(Guid portfolioId, IList<PortfolioSecuritiesRow> newDetails)
        {
            return newDetails.Any(newDetail => newDetail.IsValid && newDetail.Security != new PortfolioSecurityModel.Null()
                   && this._portfolioDetailsRepository.ReadCount(
                       x =>
                       x.PortfolioId == portfolioId && x.CompanyId == newDetail.Security.CompanyId
                       && x.SecurityId == newDetail.Security.SecurityId) > 0);
        }

        private void UpdatePortfolio(Portfolio newPortfolio, PortfolioSharingStatus oldSharingStatus)
        {
            newPortfolio.DateModified = DateTime.UtcNow;

            if (newPortfolio.SharingStatus == PortfolioSharingStatus.Private
                && oldSharingStatus != newPortfolio.SharingStatus)
            {
                if (this._screeningToolFilterManager == null)
                {
                    throw new NullReferenceException("_screeningToolFilterManager");
                }

                if (this._subscriptionsManagement == null)
                {
                    throw new NullReferenceException("_subscriptionsManagement");
                }

                // update all subscriptions
                this._subscriptionsManagement.UpdateSubsciptionsForPortfolio(
                    newPortfolio.Id,
                    newPortfolio.AddedByUserId);

                // update all filters
                this._screeningToolFilterManager.UpdateFiltersFromScreeningTool(
                    newPortfolio.Id,
                    newPortfolio.AddedByUserId,
                    newPortfolio.Name);
            }

            this._portfolioRepository.CreateOrUpdate(newPortfolio);

            _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(newPortfolio), ReplicationUtils.EntityType.Portfolio, ReplicationUtils.Operation.CreateOrUpdate);
        }

        public void MarkPortfolioMatchAsValid(Guid portfolioMatchId)
        {
            var portfolioMatch = this._portfolioMatchesRepository.ReadWhere(x => x.Id == portfolioMatchId).Single();
            portfolioMatch.Added = true;
            this._portfolioMatchesRepository.Update(portfolioMatchId, portfolioMatch);
            this._portfolioMatchesRepository.DeleteWhere(x => x.Id != portfolioMatchId
                                                            && x.DisplayInfo.SecurityIdentifier == portfolioMatch.DisplayInfo.SecurityIdentifier
                                                            && x.PortfolioName == portfolioMatch.PortfolioName
                                                            && x.AddedByUserId == portfolioMatch.AddedByUserId
                                                            && x.AccountId == portfolioMatch.AccountId
                                                            && x.GroupId == portfolioMatch.GroupId);
        }

        public void RenamePortfolioMatches(string portfolioName, string newPorfolioName, PortfolioUserClaims userClaims)
        {
            var portfolioFile = _esgInputFileRepository.GetESGFilesByFilter(x =>
                                                                           x.AccountId == userClaims.AccountId &&
                                                                           x.Type == EsgInputFileType.Portfolio &&
                                                                           x.Name == portfolioName);
            var newPortfolioFile = new EsgInputFileComplete
            {
                Id = Guid.NewGuid(),
                AccountId = portfolioFile.AccountId,
                Name = newPorfolioName,
                Type = EsgInputFileType.Portfolio,
                FileName = newPorfolioName + "_Company_Match.xlsx",
                Content = portfolioFile.Content,
                ContentType = portfolioFile.ContentType
            };


            _esgInputFileRepository.AddFile(newPortfolioFile);

            var portfolioMatches = _portfolioMatchesRepository.ReadAllWhere(x => x.PortfolioName == portfolioName &&
                                                                                 x.AddedByUserId == userClaims.UserId &&
                                                                                 x.AccountId == userClaims.AccountId);

            foreach (var portfolioMatch in portfolioMatches)
            {
                portfolioMatch.PortfolioName = newPorfolioName;
            }

            this._portfolioMatchesRepository.CreateOrUpdateBatch(portfolioMatches);
        }

        public void AddSearchedCompany(Guid companyId, string portfolioName, PortfolioUserClaims userClaims)
        {
            var id = _companyMatchingLogic.FindCapitalIqIdForGuid(companyId);
            var securityInfo = this._portfolioMatching.GetMainSecurityForCompany(id, userClaims).FirstOrDefault();

            if (securityInfo == null)
            {
                throw new PortfolioCompanyInvalidMatchException();
            }

            var securityAlreadyExists = this._portfolioMatchesRepository.ReadAllWhere(x =>
                                                            x.PortfolioName == portfolioName &&
                                                            x.MatchedInfo.CapitalIQId == securityInfo.Security.MatchedInfo.CapitalIQId &&
                                                            x.AccountId == userClaims.AccountId &&
                                                            x.AddedByUserId == userClaims.UserId).Any();

            if (securityAlreadyExists) throw new PortfolioSecurityAlreadyMatchedException();

            var match = securityInfo.Security.GetPortfolioMatches();
            match.Added = true;
            match.IsValid = true;
            match.PortfolioName = portfolioName;
            match.AddedByFullName = userClaims.UserFullName;
            match.AddedByUserId = userClaims.UserId;
            match.AddedByUserName = userClaims.UserName;
            match.AccountId = userClaims.AccountId;
            match.AddedFromCompanySearch = true;

            this._portfolioMatchesRepository.CreateOrUpdate(match);

        }

        public void RemoveSelectedPortfolioMatches(IList<Guid> portfolioMatchesIds)
        {
            this._portfolioMatchesRepository.DeleteWhere(x => portfolioMatchesIds.Contains(x.Id));
        }

        public void RemoveAllPortfolioMatches(string portfolioName, Guid userId, Guid accountId)
        {
            this._portfolioMatchesRepository.DeleteWhere(x => x.PortfolioName == portfolioName && x.AddedByUserId == userId && x.AccountId == accountId);
        }

        public void UpdateSecurityWeight(Guid portfolioMatchId, double? newValue)
        {
            var portfolioMatch = this._portfolioMatchesRepository.Read(portfolioMatchId);
            portfolioMatch.SecurityWeight = newValue;
            this._portfolioMatchesRepository.CreateOrUpdate(portfolioMatch);
        }

        public void UpdateSecurityWeight_Unmatched(Guid portfolioMatchId, double? newValue)
        {
            var portfolioMatch = this._portfolioMatchesRepository.Read(portfolioMatchId);
            var portfolioMatchDuplicates = this._portfolioMatchesRepository.ReadWhere(x => x.DisplayInfo.SecurityIdentifier == portfolioMatch.DisplayInfo.SecurityIdentifier && x.PortfolioName == portfolioMatch.PortfolioName);
            foreach (PortfolioMatches item in portfolioMatchDuplicates)
                item.SecurityWeight = newValue;
            this._portfolioMatchesRepository.CreateOrUpdateBatch(portfolioMatchDuplicates);
        }

        public void UpdatePortfolioDetailsWeight(Guid portfolioDetailId, double? newValue)
        {
            var portfolioDetail = this._portfolioDetailsRepository.Read(portfolioDetailId);
            portfolioDetail.SecurityWeight = newValue;
            this._portfolioDetailsRepository.CreateOrUpdate(portfolioDetail);
        }
    }
}